package com.pehulja.mmdo.coursework.math;

import com.google.common.primitives.Doubles;
import com.pehulja.mmdo.coursework.math.exceptions.InvalidNumberArgmentsException;
import java.io.Serializable;
import org.apache.commons.math.linear.Array2DRowRealMatrix;
import org.apache.commons.math.linear.RealMatrix;

import java.util.*;

/**
 * Class that implements linear program task functionality
 */
public class LinearProgramTask implements Serializable{

    /**
     * Main task components
     */
    private GoalFunction goalFunction;
    private List<Equation> businessRestrictions;
    private List<Equation> nonnegativityRestrictions;

    public LinearProgramTask() {
        businessRestrictions = new ArrayList<Equation>();
        nonnegativityRestrictions = new ArrayList<Equation>();
    }

    public GoalFunction getGoalFunction() {
        return goalFunction;
    }

    public void setGoalFunction(GoalFunction goalFunction) {
        this.goalFunction = goalFunction;
    }

    public List<Equation> getBusinessRestrictions() {
        return businessRestrictions;
    }

    public void setBusinessRestrictions(List<Equation> businessRestrictions) {
        this.businessRestrictions = businessRestrictions;
    }

    public List<Equation> getNonnegativityRestrictions() {
        return nonnegativityRestrictions;
    }

    public void setNonnegativityRestrictions(List<Equation> nonnegativityRestrictions) {
        this.nonnegativityRestrictions = nonnegativityRestrictions;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Целевая функция:\n" + goalFunction.toString() + "\n");
        builder.append("-------------------------------------\nОграничения:\n");
        for (Equation i : businessRestrictions) {
            builder.append(i.toString() + "\n");
        }
        builder.append("-------------------------------------\nУсловия неотрицательности\n");
        for (int i =0; i < goalFunction.getVariablesCount(); i++) {
            builder.append("x("+Integer.toString(i+1)+")>=0\n");
        }
        return builder.toString();
    }

    public LinearProgramTask copy(){
        LinearProgramTask linearProgramTask = new LinearProgramTask();
        linearProgramTask.setGoalFunction(goalFunction.copy());
        List<Equation>  equations = new ArrayList<Equation>();
        for(Equation equation:businessRestrictions)
            equations.add(equation.copy());
        linearProgramTask.setBusinessRestrictions(equations);
        return linearProgramTask;
    }
    
    /**
     * Converting task to canonical form.
     */
    public LinearProgramTask convertToCanonicalForm() {
        LinearProgramTask linearProgramTask = this.copy();
        // Harmonization of such terms in the polynomial of gf and sorting by variables ids
        linearProgramTask.goalFunction.simplify();

        ///Checking extremum of goal function:
        if (linearProgramTask.goalFunction.getExtremum() == GoalFunction.Extremum.Min) {
            linearProgramTask.goalFunction.setExtremum(GoalFunction.Extremum.Max);
            linearProgramTask.goalFunction.Multiply(-1);
        }
        for (Equation equation : linearProgramTask.getBusinessRestrictions()) {
            equation.toCanonicalForm(linearProgramTask.getVariablesIds());
        }
        linearProgramTask.goalFunction.completeToAllVars(linearProgramTask.getVariablesIds());
        for (Equation equation : linearProgramTask.getBusinessRestrictions()) {
            equation.getLeft().completeToAllVars(linearProgramTask.getVariablesIds());
        }
        return linearProgramTask;
    }

    //generetiong dual task for direct task
    public LinearProgramTask getDualTask() throws InvalidNumberArgmentsException, CloneNotSupportedException {
        LinearProgramTask dualTask = new LinearProgramTask();
        List<Term> vectorB = new ArrayList<Term>();

        //Constructiong goal function
        //Getting vector B from direct task for vector C in Dual task
        for (Equation equation : this.getBusinessRestrictions())
            vectorB.addAll(equation.getRight().getComponents());
        //Resetting variables ids in gf dual task
        for (int i = 0; i < vectorB.size(); i++)
            vectorB.get(i).setId((byte) (i + 1));
        GoalFunction gf = new GoalFunction();
        gf.getComponents().addAll(vectorB);
        gf.setExtremum(GoalFunction.Extremum.Min);
        dualTask.setGoalFunction(gf);

        //Constructing business restrinctions list
        List<Equation> br = new ArrayList<Equation>();
        List<List<Term>> dataMatrixInverse = getDataMatrixLeftPartFromBusinessRestrictions();
        dataMatrixInverse = transposeMatrix(dataMatrixInverse);
        for (List<Term> i : dataMatrixInverse)
            for (int j = 0; j < i.size(); j++)
                i.get(j).setId(vectorB.get(j).getId());
        for (int i = 0; i < dataMatrixInverse.size(); i++) {
            Equation equation = new Equation();
            equation.getLeft().getComponents().addAll(dataMatrixInverse.get(i));
            equation.getRight().getComponents().add(new Term(goalFunction.getComponents().get(i).getFactor(), 0, (byte) 0));
            equation.setInequalitySight(Equation.InequalitySight.BIGGERorEQ);
            br.add(equation);
        }
        dualTask.setBusinessRestrictions(br);
        return dualTask;

    }

    /**
     * Getting all variables ids from task according to goal function and restrictions
     * @return list of all variable ids
     */
    private List<Byte> getVariablesIds() {
        Set<Byte> list = new HashSet<Byte>();
        for (Term i : goalFunction.getComponents())
            list.add(i.getId());
        for (Equation equation : this.getBusinessRestrictions()) {
            list.addAll(equation.getLeft().getListVarsId());
        }
        return new ArrayList<Byte>(list);
    }


    /**
     * Getting max id of of all variables
     * @return
     */
    private Byte getMaxVariableId() {
        return Collections.max(getVariablesIds());
    }

    //Getting data array from business restrictions
    private List<List<Term>> getDataMatrixLeftPartFromBusinessRestrictions() throws CloneNotSupportedException {
        List<List<Term>> dataMatrix = new ArrayList<List<Term>>();
        for (int i = 0; i < this.getBusinessRestrictions().size(); i++) {
            IFunction function = this.getBusinessRestrictions().get(i).getLeft();
            dataMatrix.add(new ArrayList<Term>());
            for (int j = 0; j < function.getComponents().size(); j++)
                dataMatrix.get(i).add((Term) function.getComponents().get(j).copy());
        }
        return dataMatrix;
    }

    /**
     * Transposing matrix. It have to be square like view
     * @param dataMatrix
     * @return
     * @throws CloneNotSupportedException
     */
    private List<List<Term>> transposeMatrix(List<List<Term>> dataMatrix) throws CloneNotSupportedException {
        Term[][] transposedMatrix = new Term[dataMatrix.get(0).size()][dataMatrix.size()];

        List<List<Term>> transposedMatrixList = new ArrayList<List<Term>>();
        for (int i = 0; i < dataMatrix.size(); i++)
            for (int j = 0; j < dataMatrix.get(i).size(); j++)
                transposedMatrix[j][i] = (Term) dataMatrix.get(i).get(j).copy();
        for (Term[] row : transposedMatrix)
            transposedMatrixList.add(Arrays.asList(row));
        return transposedMatrixList;
    }

    /**
     * Getting matrix A for simplex table according to column restrictions and extended or not extended options
      * @param componentColumnRestriction
     * @param extended
     * @return
     */
    public RealMatrix getBusinessRestrictionsMatrix(List<Integer> componentColumnRestriction, boolean extended) {
        RealMatrix coeffients;
        List<Double> tempRowList;
        int elementNumberInRow = (componentColumnRestriction==null?goalFunction.getVariablesCount():componentColumnRestriction.size()) + (extended? 1 : 0);
        boolean withColemnRestrictions = componentColumnRestriction != null;
        coeffients = new Array2DRowRealMatrix(businessRestrictions.size(), elementNumberInRow);
        for (int i = 0, j = 0; i < businessRestrictions.size(); i++) {
                tempRowList = new ArrayList<Double>();
                if (extended)
                    tempRowList.addAll(businessRestrictions.get(i).getRight().getListFactors());
                tempRowList.addAll(businessRestrictions.get(i).getLeft().getListFactorsArray(componentColumnRestriction));
                coeffients.setRow(j++, Doubles.toArray(tempRowList));
        }
        return coeffients;
    }

    /**
     * Reloaded method that returns matrix for all columns
     * @param extended
     * @return
     */
    public RealMatrix getBusinessRestrictionsMatrix(boolean extended){
        return getBusinessRestrictionsMatrix(null, extended);
    }
    
    public boolean checkIfTaskHasSpecialForm(){
        boolean flag = true;
        for(Equation equation:businessRestrictions){
            flag = equation.getInequalitySight()==Equation.InequalitySight.BIGGER || equation.getInequalitySight()==Equation.InequalitySight.BIGGERorEQ;
            if(!flag)
                break;
        }
        return flag;
    }
}
